using System;

namespace Test.ConsoleProgram.Algorithm.Solution
{
    [TestDescription("算法: 0004. 寻找两个正序数组的中位数")]
    public class No0004_FindMedianSortedArrays : AbsBaseTestItem
    {
        public override void OnTest()
        {
            Assert.TestExe(FindMedianSortedArrays, new int[] { 1, 3 }, new int[] { 2 }, 2.0);
            Assert.TestExe(FindMedianSortedArrays, new int[] { 1, 2 }, new int[] { 3, 4 }, 2.5);

            Assert.TestExe(FindMedianSortedArrays2, new int[] { 1, 3 }, new int[] { 2 }, 2.0);
            Assert.TestExe(FindMedianSortedArrays2, new int[] { 1, 2 }, new int[] { 3, 4 }, 2.5);
        }

        /// <summary>
        ///  方法一：二分查找
        /// </summary>
        public double FindMedianSortedArrays(int[] nums1, int[] nums2)
        {
            if (nums1.Length > nums2.Length)
            {
                return FindMedianSortedArrays(nums2, nums1);
            }

            int m = nums1.Length;
            int n = nums2.Length;
            int left = 0, right = m, ansi = -1;
            // median1：前一部分的最大值
            // median2：后一部分的最小值
            int median1 = 0, median2 = 0;

            while (left <= right)
            {
                // 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
                // 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
                int i = (left + right) / 2;
                int j = (m + n + 1) / 2 - i;

                // nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
                int nums_im1 = (i == 0 ? int.MinValue : nums1[i - 1]);
                int nums_i = (i == m ? int.MaxValue : nums1[i]);
                int nums_jm1 = (j == 0 ? int.MinValue : nums2[j - 1]);
                int nums_j = (j == n ? int.MaxValue : nums2[j]);

                if (nums_im1 <= nums_j)
                {
                    ansi = i;
                    median1 = Math.Max(nums_im1, nums_jm1);
                    median2 = Math.Min(nums_i, nums_j);
                    left = i + 1;
                }
                else
                {
                    right = i - 1;
                }
            }

            return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
        }

        /// <summary>
        /// 方法二：划分数组
        /// </summary>
        public double FindMedianSortedArrays2(int[] nums1, int[] nums2)
        {
            int getKthElement(int[] nums1, int[] nums2, int k)
            {
                /* 主要思路：要找到第 k (k>1) 小的元素，那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
                 * 这里的 "/" 表示整除
                 * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
                 * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
                 * 取 pivot = min(pivot1, pivot2)，两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
                 * 这样 pivot 本身最大也只能是第 k-1 小的元素
                 * 如果 pivot = pivot1，那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除"，剩下的作为新的 nums1 数组
                 * 如果 pivot = pivot2，那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除"，剩下的作为新的 nums2 数组
                 * 由于我们 "删除" 了一些元素（这些元素都比第 k 小的元素要小），因此需要修改 k 的值，减去删除的数的个数
                 */

                int length1 = nums1.Length, length2 = nums2.Length;
                int index1 = 0, index2 = 0;

                while (true)
                {
                    // 边界情况
                    if (index1 == length1)
                    {
                        return nums2[index2 + k - 1];
                    }
                    if (index2 == length2)
                    {
                        return nums1[index1 + k - 1];
                    }
                    if (k == 1)
                    {
                        return Math.Min(nums1[index1], nums2[index2]);
                    }

                    // 正常情况
                    int half = k / 2;
                    int newIndex1 = Math.Min(index1 + half, length1) - 1;
                    int newIndex2 = Math.Min(index2 + half, length2) - 1;
                    int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
                    if (pivot1 <= pivot2)
                    {
                        k -= (newIndex1 - index1 + 1);
                        index1 = newIndex1 + 1;
                    }
                    else
                    {
                        k -= (newIndex2 - index2 + 1);
                        index2 = newIndex2 + 1;
                    }
                }
            };

            int length1 = nums1.Length, length2 = nums2.Length;
            int totalLength = length1 + length2;
            if (totalLength % 2 == 1)
            {
                int midIndex = totalLength / 2;
                double median = getKthElement(nums1, nums2, midIndex + 1);
                return median;
            }
            else
            {
                int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
                double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
                return median;
            }
        }
    }
}
